<?php

// Namespace

namespace Loop;


/**
 * CStringHelper is a helper that provides useful methods to manipulate and transform strings
 *
 * @package		Loop
 * @category	Helper
 */

class CStringHelper
{
	/**
	 * Transform a string like my %1 %2 string (with arraykeys = ['key1', 'key2'] and values = ['key1' => super, 'key2' => fabulous]) to my super faboulous string
	 *
	 * @param string 	$string 	String contain %1, %2...
	 * @param array 	$arrayKeys 	keys found on $values array
	 * @param array 	$values 	Containing values to replace in string
	 *
	 * @return string
	 */

	public function buildStringFromArrayKeys($string, $arrayKeys, $values)
	{
		for ($i = 1, $c = count($arrayKeys); $i <= $c; $i++)
		{
			$key = $arrayKeys[$i - 1];

			if (isset($values[$key]) === true)
			{
				$string = str_replace('%' . $i, $values[$key], $string);
			}
		}

			
		return $string;
	}


	/**
	 * Converts any type of data to XML (recursive function)
	 * Original found on http://darklaunch.com/2009/05/23/php-xml-encode-using-domdocument-convert-array-to-xml-json-encode
	 *
	 * @param	mixed		$data			The data to be converted to XML
	 * @param	DOMElement	$domElement		The current DOM element
	 * @param	DOMDocument	$domDocument	The DOM document
	 */
	
	public function convertToXml($data, $domElement = null, $domDocument = null)
	{
		if (is_null($domDocument) == true)
		{
			$domDocument = new \DOMDocument('1.0', 'utf-8');
			
			$domDocument->formatOutput = true;
			
			$node = $domDocument->createElement('loopObjects');
			$domDocument->appendChild($node);
			
			$this->convertToXml($data, $node, $domDocument);
			
			
			return $domDocument;
		}
		else
		{
			if (is_array($data))
			{
				foreach ($data as $index=>$mixedElement)
				{
					if (is_int($index) == true)
					{
							$parentNode = $domElement->tagName;
							
							if
							(
								(rtrim($parentNode, 's') !== $parentNode) &&
								($parentNode == 'loopObjects')
							)
							{
								$node = $domDocument->createElement(rtrim($parentNode, 's'));
								$domElement->appendChild($node);
							}
							else
							{
								$node = $domDocument->createElement($domElement->tagName);
								$domElement->appendChild($node);
							}
					}
					else
					{
						$node = $domDocument->createElement($index);
						$domElement->appendChild($node);
					}
					
					$this->convertToXml($mixedElement, $node, $domDocument);
				}
			}
			else
			{
				$domElement->appendChild($domDocument->createTextNode($data));
			}
		}
	}
	
	
	/**
	 * Converts a SimpleXML object into an associative array
	 * Original found on http://php.net/
	 *
	 * @param	object	$xml	A SimpleXml object
	 *
	 * @return	array			An associative array
	 */
	
	public function convertXmlToArray($xml)
	{
		// If we received an object, convert it to an array
		
		if (is_object($xml))
		{
			$xml = get_object_vars($xml);
		}
		
		
		// Convert XML into an array
		
		$array = array();
		
		if (is_array($xml))
		{
			// Parse each item in $xml
			
			foreach ($xml as $key => $value)
			{
				// If the item is an object or an array, convert it into an array
				
				if
				(
					(is_object($value) == true) ||
					(is_array($value) == true)
				)
				{
					$value = $this->convertXmlToArray($value);
				}
				
				
				// Add the item to the final array
				
				$array[$key] = $value;
			}
		}
		
		
		return $array;
	}


	/**
	 * Decrypts a string
	 *
	 * @param 	string 	$string 		The string 
	 * @param 	string 	$privateKey 	The private key 	
	 * 
	 * @return 	string 					The decrypted string
	 */

	public function decrypt($string, $privateKey)
	{
		$decryptedString = rtrim
		(
			mcrypt_decrypt
			(
				MCRYPT_RIJNDAEL_256, 
				md5($privateKey), 
				base64_decode($string), 
				MCRYPT_MODE_CBC, 
				md5
				(
					md5($privateKey)
				)
			), 
			"\0"
		);


		return $decryptedString;
	}	
	
	
	/**
	 * Encrypts a string
	 *
	 * @param	string	$string			The string
	 * @param 	boolean $biDirectional 	Can be decrypted
	 * @param 	string 	$privateKey 	private key needed to bidirectional encryption
	 *
	 * @return	string					The encrypted string
	 */
	
	public function encrypt($string, $biDirectional = false, $privateKey = null)
	{
		if ($biDirectional === false)
		{
			return sha1($string);			
		}

		if (is_null($privateKey) === true)
		{
			e
			(
				EXCEPTION_VARIABLE_NOT_FOUND,
				array
				(
					'biDirectional' => $biDirectional,
					'privateKey' => $privateKey
				)
			);
		}

		$encryptedString = base64_encode
		(
			mcrypt_encrypt
			(
				MCRYPT_RIJNDAEL_256, 
				md5($privateKey), 
				$string, 
				MCRYPT_MODE_CBC, 
				md5
				(
					md5($privateKey)
				)
			)
		);


		return $encryptedString;
	}
	
	
	/**
	 * Formats data into array type
	 * 
	 * @param 	mixed 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options
	 *
	 * @return 	mixed 					The data 
	 */
	
	public function formatArray($data, $dataOptions = null)
	{
		return print_r($data, true);
	}
	
	
	/**
	 * Formats data into boolean type
	 *
	 * @param 	mixed 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options
	 *
	 * @return 	boolean 				True if the data is true, false otherwise
	 */
	
	public function formatBoolean($data, $dataOptions = null)
	{
		return ($data == true) ? 'TRUE' : 'FALSE';
	}
	
	
	/**
	 * Formats data according to the type and current user context (e.g. language)
	 * Formatting could be automatic but as this would reduce performances significantly,
	 * developers must specify the type of data if they want any specific formatting to happen
	 *
	 * @param	mixed	$data			What needs to be formated
	 * @param	string	$dataType		The type of output (if null it will not be formatted)
	 * @param	array	$dataOptions	Contains any option required by the type of data (e.g. the currency for TYPE_PRICE)
	 *
	 * @return	string					The data once formated according to the data type
	 */
	
	public function formatData($data, $dataType = null, $dataOptions = null)
	{
		if (empty($dataType) == true)
		{
			return $this->formatHtml($data, $dataOptions);
		}
		else if ($dataType == FORMAT_ARRAY)
		{
			return $this->formatArray($data, $dataOptions);
		}
		else if ($dataType == FORMAT_BOOLEAN)
		{
			return $this->formatBoolean($data, $dataOptions);
		}
		else if ($dataType == FORMAT_RAW)
		{
			return $data;
		}
		else if ($dataType == FORMAT_DATE)
		{
			return $this->formatDate($data, $dataOptions);
		}
		else if ($dataType == FORMAT_DATETIME)
		{
			return $this->formatDateTime($data, $dataOptions);
		}
		else if ($dataType == FORMAT_DURATION)
		{
			return $this->formatDuration($data, $dataOptions);
		}
		else if ($dataType == FORMAT_MEMORY)
		{
			return $this->formatMemory($data, $dataOptions);
		}
		else if ($dataType == FORMAT_MARKDOWN)
		{
			return $this->formatMarkdown($data, $dataOptions);
		}
		else if ($dataType == FORMAT_NUMBER)
		{
			return $this->formatNumber($data, $dataOptions);
		}
		else if ($dataType == FORMAT_OBJECT)
		{
			return $this->formatObject($data, $dataOptions);
		}
		else if ($dataType == FORMAT_PRICE)
		{
			return $this->formatPrice($data, $dataOptions);
		}
		else if ($dataType == FORMAT_TIME)
		{
			return $this->formatTime($data, $dataOptions);
		}
		else if ($dataType == FORMAT_TIMESTAMP)
		{
			return $this->formatTimestamp($data, $dataOptions);
		}
		else if ($dataType == FORMAT_URL)
		{
			return $this->formatUrl($data, $dataOptions);
		}
		else if ($dataType == FORMAT_XML)
		{
			return $this->formatXml($data, $dataOptions);
		}
		else
		{
			return $data;
		}
	}
	
	
	/**
	 * Formats data into date format (e.g: 1970/01/01)
	 * 
	 * @param 	int 	$data 			The timestamp to be formated
	 * @param 	array 	$dataOptions 	Contains ['format'] => {DATE_FORMAT}
	 *
	 * @return 	string 					The formated date 
	 */
	
	public function formatDate($data, $dataOptions = null)
	{
		// Define the date format

		$format = 'd\/m\/Y';

		if (isset($dataOptions['format']) === true)
		{
			$format = $dataOptions['format'];
		}


		// Format the date according to the current language

		$languageIsoCode = pref('[Loop]/localization/languageIsoCode');
		
		if ($languageIsoCode == 'fr')
		{
			return date('d\/m\/Y', $data);
		}
		else
		{
			return date('Y\/m\/d', $data);
		}
	}
	
	
	/**
	 * Formats data into date time format (e.g: 1970/01/01 - 04:04)
	 * 
	 * @param 	int 	$data 			The timestamp to be formated
	 * @param 	array 	$dataOptions 	Contains ['format'] => {DATE_FORMAT}  
	 *
	 * @return 	string 					The formated date 
	 */
	
	public function formatDateTime($data, $dataOptions = null)
	{
		return $this->formatDate($data) . ' - ' . $this->formatTime($data);
	}
	
	
	/**
	 * Formats data into a duration type
	 * 	
	 * @param 	float 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	float 					The duration
	 */
	
	public function formatDuration($data, $dataOptions = null)
	{
		return $this->formatStep
		(
			$data,
			array('ms', 's', 'm', 'h', 'd'),
			array
			(
				1,
				60,
				3600,
				86400
			),
			array
			(
				100,
				1,
				1 / 60,
				1 / 3600,
				1 / 86400
			)
		);
	}
	
	
	/**
	 * Convert given data into HTML entities
	 * 	
	 * @param 	int 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	mixed 					The data formated
	 */
	
	public function formatHtml($data, $dataOptions = null)
	{
		if (app()->isRunningCli() == true)
		{
			// Maybe we've got an object or an array

			if (is_object($data) == true)
			{
				return $this->formatObject($data, $dataOptions);
			}

			if (is_array($data) == true)
			{
				return $this->formatArray($data, $dataOptions);
			}


			return $data;
		}
		else
		{
			// Maybe we've got an object or an array

			if (is_object($data) == true)
			{
				return '<pre>' . $this->formatObject($data, $dataOptions) . '</pre>';
			}

			if (is_array($data) == true)
			{
				return '<pre>' . $this->formatArray($data, $dataOptions) . '</pre>';
			}


			return htmlentities(stripcslashes($data), ENT_QUOTES, 'UTF-8');
		}
	}
	
	
	/**
	 * Formats data into HTML format
	 * 	
	 * @param 	int 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	string 					The data formated into Html
	 */
	
	public function formatMarkdownToHtml($data, $dataOptions = null)
	{
		// Dependencies

		require_once(PATH_LOOP . 'Component/PhpMarkdownExtra/1.2.4/markdown.php');


		return Markdown($data);
	}
	
	
	/**
	 * Formats data into a memory type
	 * 	
	 * @param 	int 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	array 					The associative array which contains [value], [metric] and [multiplier]
	 */
	
	public function formatMemory($data, $dataOptions = null)
	{
		return $this->formatStep
		(
			$data,
			array('B', 'KB', 'MB', 'GB', 'TB', 'PB'),
			array
			(
				1024,
				1024000,
				1024000000,
				1024000000000,
				1024000000000000,
				1024000000000000000
			),
			array
			(
				1,
				1 / 1024,
				1 / 1024000,
				1 / 1024000000,
				1 / 1024000000000,
				1 / 1024000000000000
			)
		);
	}
	
	
	/**
	 * Formats a number woth grouped thousands 
	 * 	
	 * @param 	float 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options ['nbDecimals'], ['isShortened'], ['stockResult']  
	 *
	 * @return  mixed 					An associative array (e.g: array('data' => 3, 'unit' => 'K') or a string (e.g: 3K) 
	 */
	
	public function formatNumber($data, $dataOptions = null)
	{
		// Make sure $dataOptions is an array
		
		if (is_array($dataOptions) == false)
		{
			$dataOptions = array();
		}

		
		// Make sure $dataOptions contains the right values
		
		$defaultDataOptions = array
		(
			'nbDecimals' => 2,
			'isShortened' => true,
			'stockResult' => false
		);
		
		$dataOptions = array_merge($defaultDataOptions, $dataOptions);
		
		
		// Let's format!
		
		$suffix = null;
		
		if ($dataOptions['isShortened'] == true)
		{
			if
			(
				($data >= 10000) &&
				($data < 1000000)
			)
			{
				$data /= 1000;
				$suffix = 'K';
			}
			else if
			(
				($data >= 1000000) &&
				($data < 1000000000)
			)
			{
				$data /= 1000000;
				$suffix = 'M';
			}
			else if
			(
				($data >= 1000000000) &&
				($data < 1000000000000)
			)
			{
				$data /= 1000000000;
				$suffix = 'B';
			}
		}
		
		
		// If the number is an integer, no need for decimals
		
		if (round($data) == $data)
		{
			$dataOptions['nbDecimals'] = 0;
		}
		
		
		if ($dataOptions['stockResult'] == false)
		{
			// Format the number

			$result = number_format($data, $dataOptions['nbDecimals']) . $suffix;
		}
		else
		{
			// Return the number formatted

			$data = number_format($data, $dataOptions['nbDecimals']);

			$result = array
			(
				'data' => $data,
				'unit' => $suffix
			);
		}
		
		
		return $result;
	}
	
	
	/**
	 * Formats data into an object format
	 * 	
	 * @param 	object 	$data 			The object to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	mixed 					The data formated
	 */
	
	public function formatObject($data, $dataOptions = null)
	{
		// Objects extending CObject have a special treatment
		
		if (get_parent_class($data) == 'CObject')
		{
			$rawObject = $data->convertToRawObject();
			$data = array
			(
				'className' => get_class($data),
				'rawObject' => $rawObject
			);
		}


		return print_r($data, true);
	}
	
	
	/**
	 * Formats data into a price format (e.g : $25.50)
	 * 	
	 * @param 	float 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options ['nbDecimals'], ['symbol']
	 *
	 * @return 	string 					The data formated (e.g: $25.0 OR 25.50€)
	 */
	
	public function formatPrice($data, $dataOptions = null)
	{
		$result = number_format($data, $dataOptions['nbDecimals']);
		
		$languageIsoCode = pref('[Loop]/localization/languageIsoCode');

		if ($languageIsoCode == 'fr')
		{
			return $result . $dataOptions['symbol'];
		}
		else
		{
			return $dataOptions['symbol'] . $result;
		}
	}


	/**
	 * Formats data
	 * 	
	 * @param 	float 	$data 			What needs to be formated
	 * @param 	array	$metrics  		The array of metric
	 * @param 	array 	$limits 		The array of limit
	 * @param 	array 	$multipliers 	The array of multipliers
	 *
	 * @return 	array 					The associative array (e.g: array('value' => 3.59, 'metric' => s, 'multiplier' => 1)) 
	 */

	public function formatStep($data, $metrics, $limits, $multipliers)
	{
		// So which metric / multiplier does it match?

		$i = 0;

		foreach ($limits as $limit)
		{
			if ($data < $limit)
			{
				break;
			}

			$i++;
		}

		
		// Prepare the result
		
		$result = array
		(
			'value' => round($data * $multipliers[$i], 2),
			'metric' => $metrics[$i],
			'multiplier' => $multipliers[$i]
		);
				
		
		return $result;
	}
	
	
	/**
	 * Formats data into time format (e.g: 04:04)
	 * 
	 * @param 	int 	$data 			The timestamp to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return 	string 					The formated hour (e.g: 04h04 OR 04:04) 
	 */
	
	public function formatTime($data, $dataOptions = null)
	{
		$languageIsoCode = pref('[Loop]/localization/languageIsoCode');
		
		if ($languageIsoCode == 'fr')
		{
			return date('H\hi', $data);
		}
		else
		{
			return date('H:i', $data);
		}
	}
	
	
	/**
	 * Formats data into date format
	 * 	
	 * @param 	int 	$data 			The timestamp to be formated
	 * @param 	array 	$dataOptions 	Contains some options  (e.g: array('format' => 'Y-m-d H:i:s'))
	 *
	 * @return 	string 					The  formated date
	 */
	
	public function formatTimestamp($data, $dataOptions = null)
	{
		// Make sure we have options
		
		$defaultDataOptions = array
		(
			'format' => 'Y-m-d\TH:i:s\Z'
		);
		
		$dataOptions = array_merge($defaultDataOptions, $dataOptions);
		

		return date($dataOptions['format'], $data);
	}
	
	
	/**
	 * Url encoded data  
	 * 	
	 * @param 	string 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return  string 					The url encoded  
	 */
	
	public function formatUrl($data, $dataOptions = null)
	{
		return urlencode($data);
	}
	
	
	/**
	 * Formats data into xml
	 * 	
	 * @param 	mixed 	$data 			What needs to be formated
	 * @param 	array 	$dataOptions 	Contains some options  
	 *
	 * @return  string 					The formated data
	 */
	
	public function formatXml($data, $dataOptions = null)
	{
		return str_replace
		(
			array
			(
				'%',
				'<',
				'>',
				'"'
			),
			array
			(
				'%25',
				'&lt;',
				'&gt;',
				'&quot;'
			),
			$data
		);
	}
	
	
	/**
	 * Generates a UUID
	 *
	 * @return	string	A 36 chararacters unique ID
	 */
	
	public function generateUuid()
	{
		// Dependencies
		
		require_once(PATH_LOOP . 'Component/Uuid/UUID.php');
		
		
		// Generate a UUID (v4)
		
		$uuid = \UUID::v4();
		
		
		return $uuid;
	}
	
	
	/**
	 * Generates a unique key
	 *
	 * @param	bool	$isLong	If true, the key will be long, otherwise will be short
	 */
	
	public function generateUniqueKey($length = 32)
	{
		$key = md5(uniqid(uniqid(mt_rand(), true), true));
		
		if ($length < 32)
		{
			return substr($key, 0, $length);
		}
		

		return $key;
	}


	/**
	 *	Get the depth of given Array
	 *
	 *	@param	array  $array	The array that we want to get the depth value
	 *
	 *  @return	int $maxDepth	The depth value of the array
	 */ 

	public function getArrayDepth($array)
	{
		if
		(
			(is_array($array) == false) &&
			(is_object($array) == false)
		)
		{
			return;
		}


		// Set the default depth value

		$maxDepth = 1;

    	
    	// Foreach value of the given array we check if it's an array

    	foreach ($array as $value) 
    	{
        	if
        	(
        		(is_array($value) === true) ||
        		(is_object($value) === true)
        	)
        	{
        		// If the value is an array we call the function on it

            	$currentDepth = $this->getArrayDepth($value) + 1;

	            
	            // If the current depth is over the max depth, the max depth is updated else we stop the progression

	            if ($currentDepth > $maxDepth) 
	            {
	                $maxDepth = $currentDepth;
	            }
        	}
    	}

    	return $maxDepth;
	}


	/**
	 * Replaces the unicode sequences into utf-8 characters in JSON string
	 *
	 *	@param	string  $jsonData	The given json string
	 *
	 *  @return	string  The json string result without unicode sequences
	 */	

	public function jsonRemoveUnicodeSequences($jsonData) 
	{

		if (empty($jsonData) == true)
		{
			true;
		}	


   		return preg_replace("/\\\\u([a-f0-9]{4})/e", "iconv('UCS-4LE','UTF-8',pack('V', hexdec('U$1')))", $jsonData);
	}


	/**
	 * Replace elements of the string ex: this is my string with %1 and %2 - array('argument1', 'argument2')
	 *
	 * @param	string	$string		The string to be replace
	 * @param	array	$args		The replace value
	 *
	 * @return	string	$string		The final string
	 */
	
	public function replaceString($string, $args = array())
	{
		// Preg_replace_callback call the anonym function every time this one find the pattern

		$string = preg_replace_callback
		(   
	        '/(%+(\d+))/',
	        function($matches) use ($args)
	        {
	        	return  $args[( $matches[2] - 1 )];	
	        },
	        $string   
		);
		
		return $string;
	}
	
	
	/**
	 * Truncates a string if above a given length
	 *
	 * @param	string	$string	The string to be truncated
	 * @param	int		$length	The maximum length of the string
	 *
	 * @return	string			The final string (can be unchanged if length of original string shorter than $length)
	 */
	
	public function truncateString($string, $length)
	{
		// Truncate the string only if longer than $length
		
		if (strlen($string) > $length)
		{
			$string = trim(substr($string, 0, $length)) . '...';
		}
		
		
		return $string;
	}
}

?>
